/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @oncall memory_lab
 */
import type {ParsedArgs} from 'minimist';
import type {
  BaseOption,
  CLIOptions,
  CommandOptionExample,
  Optional,
} from '@memlab/core';

import BaseCommand, {CommandCategory} from '../../BaseCommand';
import {analysis, config, fileManager, runInfoUtils} from '@memlab/core';
import BaselineFileOption from '../../options/heap/BaselineFileOption';
import FinalFileOption from '../../options/heap/FinalFileOption';
import JSEngineOption from '../../options/heap/JSEngineOption';
import SnapshotDirectoryOption from '../../options/heap/SnapshotDirectoryOption';
import TargetFileOption from '../../options/heap/TargetFileOption';
import InitDirectoryCommand from '../InitDirectoryCommand';
import OversizeThresholdOption from '../../options/heap/OversizeThresholdOption';
import TraceAllObjectsOption from '../../options/heap/TraceAllObjectsOption';
import LogTraceAsClusterOption from '../../options/heap/LogTraceAsClusterOption';
import CleanLoggerDataCommand from '../CleanLoggerDataCommand';
import LeakFilterFileOption from '../../options/heap/leak-filter/LeakFilterFileOption';
import LeakClusterSizeThresholdOption from '../../options/heap/LeakClusterSizeThresholdOption';
import MLClusteringOption from '../../options/MLClusteringOption';
import MLClusteringLinkageMaxDistanceOption from '../../options/MLClusteringLinkageMaxDistanceOption';
import MLClusteringMaxDFOption from '../../options/MLClusteringMaxDFOption';
import CleanupSnapshotOption from '../../options/heap/CleanupSnapshotOption';
import SetWorkingDirectoryOption from '../../options/SetWorkingDirectoryOption';
import OptionConstant from '../../options/lib/OptionConstant';
import HeapParserDictFastStoreSizeOption from '../../options/heap/HeapParserDictFastStoreSizeOption';

export type CheckLeakCommandOptions = {
  isMLClustering?: boolean;
};

export default class CheckLeakCommand extends BaseCommand {
  private isMLClustering = false;
  private isMLClusteringSettingCache = false;

  protected useDefaultMLClusteringSetting(cliArgs: ParsedArgs): void {
    if (!MLClusteringOption.hasOptionSet(cliArgs)) {
      config.isMLClustering = this.isMLClustering;
      this.isMLClusteringSettingCache = config.isMLClustering;
    }
  }

  protected restoreDefaultMLClusteringSetting(cliArgs: ParsedArgs): void {
    if (!MLClusteringOption.hasOptionSet(cliArgs)) {
      config.isMLClustering = this.isMLClusteringSettingCache;
    }
  }

  constructor(options: CheckLeakCommandOptions = {}) {
    super();
    this.isMLClustering = !!options?.isMLClustering;
  }

  getCommandName(): string {
    return 'find-leaks';
  }

  getExamples(): CommandOptionExample[] {
    const optionNames = OptionConstant.optionNames;
    const workDirOption = `--${optionNames.WORK_DIR}`;
    const snapshotDirOption = `--${optionNames.SNAPSHOT_DIR}`;
    const baselineOption = `--${optionNames.BASELINE}`;
    const targetOption = `--${optionNames.TARGET}`;
    const finalOption = `--${optionNames.FINAL}`;
    return [
      {
        description:
          'check memory leaks in the default working directory generated by\n' +
          `memlab run (without setting the ${workDirOption} option)`,
        cliOptionExample: '', // default empty command options
      },
      {
        description:
          'specify the baseline, target, and final heap snapshot file path separately',
        cliOptionExample: `${baselineOption} /tmp/baseline.heapsnapshot ${targetOption} /tmp/target.heapsnapshot ${finalOption} /tmp/final.heapsnapshot`,
      },
      {
        description:
          'specifies the directory that contains all three heap snapshot files',
        cliOptionExample: `${snapshotDirOption} /dir/containing/heapsnapshot/files/`,
      },
      {
        description:
          'specifies the output working directory of the `memlab run` or the `memlab snapshot` command',
        cliOptionExample: `${workDirOption} /memlab/working/dir/generated/by/memlab/`,
      },
    ];
  }

  getDescription(): string {
    return 'find memory leaks in heap snapshots';
  }

  getDocumentation(): string {
    const optionNames = OptionConstant.optionNames;
    const workDirOption = `--${optionNames.WORK_DIR}`;
    const snapshotDirOption = `--${optionNames.SNAPSHOT_DIR}`;
    const baselineOption = `--${optionNames.BASELINE}`;
    const targetOption = `--${optionNames.TARGET}`;
    const finalOption = `--${optionNames.FINAL}`;

    return `There are three ways to specify inputs for the \`memlab ${this.getCommandName()}\` command:
 1. \`${baselineOption}\`, \`${targetOption}\`, \`${finalOption}\` specifies each heap snapshot input individually;
 2. \`${snapshotDirOption}\` specifies the directory that contains all three heap snapshot files (MemLab will assign baseline, target, and final based on alphabetic order of the file);
 3. \`${workDirOption}\` specifies the output working directory of the \`memlab run\` or the \`memlab snapshot\` command;

Please only use one of the three ways to specify the input.

You can also manually take heap snapshots in Chrome Devtools, save them to disk.
Then process them using this command with the CLI flags (either option 1
or option 2 mentioned above).
`;
  }

  getCategory(): CommandCategory {
    return CommandCategory.COMMON;
  }

  getPrerequisites(): BaseCommand[] {
    return [new InitDirectoryCommand(), new CleanLoggerDataCommand()];
  }

  getOptions(): BaseOption[] {
    return [
      new BaselineFileOption(),
      new TargetFileOption(),
      new FinalFileOption(),
      new SnapshotDirectoryOption(),
      new JSEngineOption(),
      new LeakFilterFileOption(),
      new OversizeThresholdOption(),
      new LeakClusterSizeThresholdOption(),
      new TraceAllObjectsOption(),
      new LogTraceAsClusterOption(),
      new MLClusteringOption(),
      new MLClusteringLinkageMaxDistanceOption(),
      new MLClusteringMaxDFOption(),
      new CleanupSnapshotOption(),
      new SetWorkingDirectoryOption(),
      new HeapParserDictFastStoreSizeOption(),
    ];
  }

  async run(options: CLIOptions): Promise<void> {
    const workDir = options.configFromOptions?.workDir as Optional<string>;
    fileManager.initDirs(config, {workDir});
    const {runMetaInfoManager} = runInfoUtils;
    runMetaInfoManager.setConfigFromRunMeta({
      workDir,
      silentFail: true,
    });

    config.chaseWeakMapEdge = false;

    this.useDefaultMLClusteringSetting(options.cliArgs);
    await analysis.checkLeak();
    this.restoreDefaultMLClusteringSetting(options.cliArgs);

    const configFromOptions = options.configFromOptions ?? {};
    if (configFromOptions['cleanUpSnapshot']) {
      fileManager.removeSnapshotFiles();
    }
  }
}
